/**
 * Copyright (C) 2009 Noel Ang
 *
 * This file is part of Samsara.
 *
 * Samsara is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Samsara is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Samsara.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.lunarshades.samsara.ui.actions;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Collection;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.lunarshades.samsara.Raider;
import com.lunarshades.samsara.ui.RosterTableModel;
import com.lunarshades.samsara.ui.Utils;

/**
 * Action responsible for associating (additional) names to entries in a
 * RosterTableModel.
 *
 * @author Noel Ang, sparta.variable@gmail.com
 * @see RosterTableModel
 */
public class RaiderAddNameAction<E extends Raider> extends RosterChangeAction
        implements ListSelectionListener {

public RaiderAddNameAction(RosterTableModel<E> model, JTable table) {
    super(NAME);

    if (model == null) {
        throw new NullPointerException("model");
    }
    mModel = model;

    if (table == null) {
        throw new NullPointerException("table");
    }
    mTable = table;

    putValue(Action.SHORT_DESCRIPTION, SHORT_TEXT);
    putValue(Action.MNEMONIC_KEY, KeyEvent.VK_N);

    setEnabled(!table.getSelectionModel().isSelectionEmpty()
            && table.getSelectionModel().getMinSelectionIndex()
            == table.getSelectionModel().getMaxSelectionIndex()
    );

    // Listen to selection changes to enable/disable the action as appropriate
    table.getSelectionModel().addListSelectionListener(this);
}

public void actionPerformed(ActionEvent e) {
    int[] selectedRows = mTable.getSelectedRows();
    assert selectedRows.length == 1;

    if (selectedRows.length > 0) {
        int modelRow = mTable.convertRowIndexToModel(selectedRows[0]);
        synchronized (mModel) {
            E entry = mModel.getRowAt(modelRow);
            JDialog dialog = createDialog(entry, mModel);
            dialog.pack();
            dialog.setVisible(true);
        }
    }
}

private synchronized JDialog createDialog(final E entry,
                                          final RosterTableModel<E> rosterModel
) {
    assert entry != null;

    JPanel panel = new JPanel();

    JComboBox nameComboBox = new JComboBox(entry.names().toArray());
    nameComboBox.setEditable(true);
    nameComboBox.setSelectedItem(entry.name());
    nameComboBox.getEditor().selectAll();

    JLabel nameLabel = new JLabel(NAME_LABEL);
    nameLabel.setLabelFor(nameComboBox);
    nameLabel.setDisplayedMnemonic(NAME_LABEL_MNEMONIC);

    panel.add(nameLabel);
    panel.add(nameComboBox);

    JButton button;
    Collection<Component> options = new ArrayList<Component>();
    button = new JButton(new SetNameAction(entry, rosterModel, nameComboBox));
    button.setDefaultCapable(true);
    options.add(button);
    button =
            new JButton(new RemoveNameAction(entry, rosterModel, nameComboBox));
    button.setDefaultCapable(false);
    options.add(button);
    Utils.equalizePreferredSizes(options);

    JOptionPane optionPane = new JOptionPane(panel, JOptionPane.PLAIN_MESSAGE);
    optionPane.setOptions(options.toArray(new Component[options.size()]));

    return optionPane.createDialog(mTable, String.valueOf(getValue(Action.NAME))
    );
}

public void valueChanged(ListSelectionEvent e) {
    ListSelectionModel selectModel = mTable.getSelectionModel();
    setEnabled(!selectModel.isSelectionEmpty()
            && selectModel.getMinSelectionIndex()
            == selectModel.getMaxSelectionIndex()
    );
}

private abstract class NamingAction extends AbstractAction {
    protected NamingAction(String name,
                           E subject,
                           RosterTableModel<E> rosterModel
    ) {
        super(name);
        assert subject != null;
        assert rosterModel != null;

        this.subject = subject;
        subjects = rosterModel;
    }

    protected boolean isNameReserved(String name) {
        boolean isReserved;
        assert name != null;
        assert !"".equals(name.trim());

        // Check against Subject's own names
        isReserved = subject.hasName(name);

        // Check against entire roster
        if (!isReserved) {
            for (int i = 0, last = subjects.getRowCount();
                 !isReserved && i < last;
                 ++i) {
                isReserved = subjects.getRowAt(i).hasName(name);
            }
        }

        return isReserved;
    }

    protected boolean isProtectedName(String name) {
        return subject.id().equalsIgnoreCase(name.trim());
    }

    protected boolean isActiveName(String name) {
        return subject.name().equalsIgnoreCase(name.trim());
    }

    protected void fireModelUpdated() {
        for (int i = 0, count = subjects.getRowCount(); i < count; ++i) {
            E rowEntry = subjects.getRowAt(i);
            if (subject.equals(rowEntry)) {
                subjects.fireTableRowsUpdated(i, i);
                break;
            }
        }
    }

    protected final E subject;
    protected final RosterTableModel<E> subjects;
}

private class SetNameAction extends NamingAction implements KeyListener {
    private SetNameAction(E subject,
                          RosterTableModel<E> rosterModel,
                          JComboBox comboBox
    ) {
        super(ACTION_NAME, subject, rosterModel);
        assert subject != null;
        assert rosterModel != null;
        assert comboBox != null;
        view = comboBox;
        view.setSelectedItem(subject.name());
        view.addActionListener(this);
        view.getEditor().getEditorComponent().addKeyListener(this);
        putValue(Action.MNEMONIC_KEY, ACTION_MNEMONIC_KEY);
        setEnabled(false);
    }

    private void setPrimaryName(String name) {
        assert name != null;
        subject.name(name);
        String actualName = subject.name();
        setSelectedName(actualName);
        view.getEditor().getEditorComponent().requestFocusInWindow();
        setEnabled(false);
        fireModelUpdated();
    }

    private void setSelectedName(String actualName) {
        view.setSelectedItem(actualName);
        boolean isNewName = true;
        for (int i = 0, len = view.getItemCount(); isNewName && i < len; ++i) {
            Object item = view.getItemAt(i);
            if (item != null) {
                isNewName =
                        !item.toString().trim().equalsIgnoreCase(actualName);
            }
        }
        if (isNewName) {
            view.addItem(actualName);
        }
    }

    private void updateEnabledState(Object selection) {
        String name = (selection != null ? selection.toString().trim() : "");

        // Short-circuit isNameReserved call, because it's expensive
        setEnabled(!"".equals(name)
                && !isActiveName(name)
                && (subject.hasName(name)
                || !isNameReserved(name))
        );
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            Object item = view.getEditor().getItem();
            if (item != null) {
                setPrimaryName(item.toString().trim());
            }
        }
    }

    public void keyReleased(KeyEvent e) {
        Object item = view.getEditor().getItem();
        updateEnabledState(item);
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == view) {
            // Responding to editing or selection event
            Object item = view.getSelectedItem();
            updateEnabledState(item);
        } else {
            // Responding to command event
            Object item = view.getSelectedItem();
            if (item != null) {
                setPrimaryName(item.toString().trim());
            }
        }
    }

    private static final String ACTION_NAME = "Set";
    private static final int ACTION_MNEMONIC_KEY = KeyEvent.VK_E;
    private final JComboBox view;
}

private class RemoveNameAction extends NamingAction implements KeyListener {
    public RemoveNameAction(E subject,
                            RosterTableModel<E> rosterModel,
                            JComboBox comboBox
    ) {
        super(ACTION_NAME, subject, rosterModel);
        assert subject != null;
        assert rosterModel != null;
        assert comboBox != null;
        view = comboBox;
        view.addActionListener(this);
        view.getEditor().getEditorComponent().addKeyListener(this);
        putValue(Action.MNEMONIC_KEY, ACTION_MNEMONIC_KEY);

        Object selection = view.getSelectedItem();
        updateEnabledState(selection);
    }

    private void updateEnabledState(Object selection) {
        String name = (selection != null ? selection.toString().trim() : "");
        int itemCount = view.getItemCount();
        setEnabled(itemCount > 1
                && !"".equals(name)
                && !isProtectedName(name)
                && subject.hasName(name)
        );
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
    }

    public void keyReleased(KeyEvent e) {
        Object item = view.getEditor().getItem();
        updateEnabledState(item);
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == view) {
            // Responding to editing or selection event
            Object item = view.getSelectedItem();
            updateEnabledState(item);
        } else {
            // Responding to action command
            Object item = view.getSelectedItem();
            String name = (item != null ? item.toString().trim() : "");
            int itemCount = view.getItemCount();
            if (!"".equals(name) && isNameReserved(name) && itemCount > 1) {
                String previousPrimaryName = subject.name();
                view.removeItem(item);
                subject.unname(name);
                if (previousPrimaryName.equalsIgnoreCase(name)) {
                    view.setSelectedItem(subject.name());
                }
                setEnabled(false);
                fireModelUpdated();
            }
        }
    }

    private static final String ACTION_NAME = "Del";
    private static final int ACTION_MNEMONIC_KEY = KeyEvent.VK_D;
    private final JComboBox view;
}

private static final String NAME = "Manage Names";
private static final String SHORT_TEXT =
        "Set, add, delete names for the selected entry";
private static final String NAME_LABEL = "Name: ";
private static final char NAME_LABEL_MNEMONIC = 'n';

private final JTable mTable;
private final RosterTableModel<E> mModel;
}
